package goclient

import (
	"errors"
	"math"
	"net/http"
	"time"
)

var ContentLengthTooLarge = errors.New("content length too large")

func NewKcpHTTPClient() *HTTPClient {
	tr := &http.Transport{
		Proxy:                 http.ProxyFromEnvironment,
		ForceAttemptHTTP2:     true,
		MaxIdleConns:          128,
		MaxIdleConnsPerHost:   2,
		MaxConnsPerHost:       4,
		IdleConnTimeout:       120 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 3 * time.Second,
		DialContext:           kcpDialContext,
	}
	return &HTTPClient{
		client: &http.Client{Transport: tr, Timeout: time.Second * 30},
	}
}

func NewHTTPClient() *HTTPClient {
	tr := &http.Transport{
		Proxy:                 http.ProxyFromEnvironment,
		ForceAttemptHTTP2:     true,
		MaxIdleConns:          128,
		MaxIdleConnsPerHost:   2,
		MaxConnsPerHost:       4,
		IdleConnTimeout:       120 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 3 * time.Second,
	}
	return &HTTPClient{
		client: &http.Client{Transport: tr, Timeout: time.Second * 30},
	}
}

type HTTPClient struct {
	client      *http.Client
	MaxBodySize int32
}

func (c *HTTPClient) Close() {
	c.client.CloseIdleConnections()
}

func (c *HTTPClient) Get(url string) (*Response, error) {
	return c.DoRequest(http.MethodGet, url, nil, nil)
}

func (c *HTTPClient) GetWithHeader(url string, header *Header) (*Response, error) {
	return c.DoRequest(http.MethodGet, url, header, nil)
}

func (c *HTTPClient) Head(url string) (*Response, error) {
	return c.DoRequest(http.MethodHead, url, nil, nil)
}

func (c *HTTPClient) HeadWithHeader(url string, header *Header) (*Response, error) {
	return c.DoRequest(http.MethodHead, url, header, nil)
}

func (c *HTTPClient) Post(url string, contentType string, body ReadCloser) (*Response, error) {
	header := NewHeader()
	header.Set("Content-Type", contentType)
	return c.DoRequest(http.MethodPost, url, header, body)
}

func (c *HTTPClient) PostWithHeader(url string, header *Header, body ReadCloser) (*Response, error) {
	return c.DoRequest(http.MethodPatch, url, header, body)
}

func (c *HTTPClient) Put(url string, contentType string, body ReadCloser) (*Response, error) {
	header := NewHeader()
	header.Set("Content-Type", contentType)
	return c.DoRequest(http.MethodPut, url, header, body)
}

func (c *HTTPClient) PutWithHeader(url string, header *Header, body ReadCloser) (*Response, error) {
	return c.DoRequest(http.MethodPut, url, header, body)
}

func (c *HTTPClient) Patch(url string, contentType string, body ReadCloser) (*Response, error) {
	header := NewHeader()
	header.Set("Content-Type", contentType)
	return c.DoRequest(http.MethodPatch, url, header, body)
}

func (c *HTTPClient) PatchWithHeader(url string, header *Header, body ReadCloser) (*Response, error) {
	return c.DoRequest(http.MethodPatch, url, header, body)
}

func (c *HTTPClient) Delete(url string, contentType string, body ReadCloser) (*Response, error) {
	header := NewHeader()
	header.Set("Content-Type", contentType)
	return c.DoRequest(http.MethodDelete, url, header, body)
}

func (c *HTTPClient) DeleteWithHeader(url string, header *Header, body ReadCloser) (*Response, error) {
	return c.DoRequest(http.MethodDelete, url, header, body)
}

func (c *HTTPClient) DoRequest(method string, url string, header *Header, body ReadCloser) (*Response, error) {
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return nil, err
	}
	if header != nil {
		header.copyTo(&req.Header)
	}
	res, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}
	return c.makeResponse(res)
}

func (c *HTTPClient) makeResponse(res *http.Response) (*Response, error) {
	if math.MaxInt32 < res.ContentLength {
		res.Body.Close()
		return nil, ContentLengthTooLarge
	}
	contentLength := int32(res.ContentLength)
	header := &Header{header: &res.Header}
	return &Response{
		StatusCode:    int32(res.StatusCode),
		Status:        res.Status,
		ContentType:   res.Header.Get("Content-Type"),
		ContentLength: contentLength,
		Body:          res.Body,
		Header:        header,
	}, nil
}
